﻿using System;
using System.Threading;
using LogikBug.Async.Tasks.Entities;

namespace LogikBug.Async.Tasks
{
	/// <summary>
	/// Provides a base class for all <see cref="Agent"/>s to derive from.
	/// </summary>
	public abstract class Agent : DisposableObject
	{
		#region Events

		/// <summary>
		/// Invoked when the this <see cref="Agent"/> starts.
		/// </summary>
		public event Action<AgentContext> OnStarted;

		/// <summary>
		/// Invoked when the this <see cref="Agent"/> is disposing.
		/// </summary>
		public event Action<AgentContext, AgentResult> OnDisposing;

		/// <summary>
		/// Invoked when the this <see cref="Agent"/> has completed.
		/// </summary>
		public event Action<AgentContext, AgentResult> OnCompleted;

		/// <summary>
		/// Invoked when the this <see cref="Agent"/> has thrown an <see cref="Exception"/>.
		/// </summary>
		public event Action<AgentContext, AgentResult, Exception> OnErrored;

		#endregion

		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="Agent"/> class.
		/// </summary>
		protected Agent()
		{
			AgentId = Guid.NewGuid();
			Result = new AgentResult();
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets a unique identifier for the current instance.
		/// </summary>
		public Guid AgentId { get; private set; }

		/// <summary>
		/// Gets or sets the context in which this instance is executing.
		/// </summary>
		public AgentContext Context { get; set; }

		/// <summary>
		/// Gets the <see cref="Result"/> of the
		/// execution of the current <see cref="Agent"/>.
		/// </summary>
		public AgentResult Result { get; protected set; }

		/// <summary>
		/// Gets the unique identifier for the current <see cref="Agent"/>'s <see cref="Type"/>.
		/// <remarks>
		/// This value is set using the <see cref="System.Runtime.InteropServices.GuidAttribute"/>.
		/// </remarks>
		/// </summary>
		public Guid TypeId
		{
			get
			{
				return GetType().GUID;
			}
		}

		#endregion

		#region Methods

		/// <summary>
		/// Disposes the resources associated with this current instance
		/// and invokes the event <see cref="OnDisposing"/> which signals
		/// the controller that this <see cref="Agent"/> is currently disposing.
		/// </summary>
		protected override void Dispose(bool disposing)
		{
			if (disposing)
			{
				Result.DisposingTime = DateTime.Now;

				if (OnDisposing != null)
				{
					OnDisposing(Context, Result);
				}
			}
		}

		/// <summary>
		/// The entry point for the current <see cref="Thread"/>.
		/// <remarks>
		/// This method is passed into the constructor of the <see cref="Thread"/>
		/// contained within this <see cref="Agent"/>'s <see cref="AgentContext"/>.
		/// This is the starting point for execution of the current <see cref="Agent"/>.
		/// This method should not be call from any other path.
		/// </remarks>
		/// </summary>
		public virtual void Activate()
		{
			try
			{
				if (this.Context.AgentSettings.UseThreadPool)
				{
					Context.Thread = Thread.CurrentThread;
				}

				InvokeStarted();
				Execute();
				Result.IsSuccessful = true;
			}
			catch (ThreadAbortException)
			{
			}
			catch (Exception e)
			{
				InvokeErrored(e);
			}
			finally
			{
				InvokeCompleted();
			}
		}

		/// <summary>
		/// Invokes the <see cref="OnStarted"/> event.
		/// </summary>
		public void InvokeStarted()
		{
			Result.StartTime = DateTime.Now;

			if (OnStarted != null)
			{
				OnStarted(Context);
			}
		}

		/// <summary>
		/// Invokes the <see cref="OnCompleted"/> event.
		/// </summary>
		public void InvokeCompleted()
		{
			Result.CompletionTime = DateTime.Now;

			if (OnCompleted != null)
			{
				OnCompleted(Context, Result);
			}
		}

		/// <summary>
		/// Invokes the <see cref="OnErrored"/> event.
		/// </summary>
		public void InvokeErrored(Exception e)
		{
			Result.IsSuccessful = false;

			if (OnErrored != null)
			{
				OnErrored(Context, Result, e);
			}
		}

		/// <summary>
		/// Gets the timeout message from the 
		/// AppSetting: 'Agent.AgentRanForLongerThanMessage'.
		/// If the AppSetting value is nonexistent, then the
		/// resources: 'AgentTimeoutExceptionMessage' is used.
		/// </summary>
		/// <returns></returns>
		public virtual string GetTimeoutMessage()
		{
			return string.Format(
				"The '{0}' timed out after {1}.",
				GetType().FullName,
				Context.AgentSettings.ExecutionTimeout
			);
		}

		/// <summary>
		/// When overridden in a derived class, performes
		/// all the functionality for the current <see cref="Agent"/>.
		/// <remarks>
		/// This method is called from within the <see cref="Activate"/>
		/// method and should not be called from another path.
		/// </remarks>
		/// </summary>
		public abstract void Execute();

		#endregion
	}

	/// <summary>
	/// Provides a base class for all <see cref="Agent"/>s to
	/// derive from and provides a strongly typed <see cref="Model"/>.
	/// </summary>
	/// <typeparam name="TModel">The <typeparamref name="TModel"/> inheriting
	/// from <see cref="Model"/> to be used with this current instance.</typeparam>
	public abstract class Agent<TModel> : Agent, IAgentModel<TModel>
	{
		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="Agent{TModel}"/>
		/// class with the provided <paramref name="model"/>.
		/// </summary>
		/// <param name="model">
		/// The <typeparamref name="TModel"/>
		/// used to initialize this instance with.
		/// </param>
		protected Agent(TModel model)
		{
			Model = model;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the <typeparamref name="TModel"/> for this instance.
		/// </summary>
		public TModel Model { get; protected set; }

		#endregion

		#region Methods

		#region IAgentModel Members

		/// <summary>
		/// Gets the <see cref="Model"/> if it
		/// inherits from <typeparamref name="T"/>.
		/// </summary>
		/// <typeparam name="T">The type of model expected.</typeparam>
		/// <returns>The <see cref="Model"/> if it
		/// inherits from <typeparamref name="T"/>.</returns>
		public T GetModel<T>()
		{
			object model = Model;

			if (model is T)
			{
				return (T)model;
			}

			return default(T);
		}

		/// <summary>
		/// Returns True if the model is of type
		/// <typeparamref name="T"/>, otherwise False.
		/// </summary>
		/// <typeparam name="T">The type expected.</typeparam>
		/// <returns>True if the model is of type
		/// <typeparamref name="T"/>, otherwise False.</returns>
		public bool ModelIs<T>()
		{
			return Model is T;
		}

		#endregion

		#endregion
	}
}
